﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using VelocityDb;
using VelocityDb.Session;

namespace RESTester
{
    public class RESTesterLogic
    {
        private int _lastId;

        private List<string> _restrictedHeaders = new List<string>
                                                        {
                                                            "Accept",
                                                            "Connection",
                                                            "Content-Length",
                                                            "Content-Type",
                                                            "Date",
                                                            "Expect",
                                                            "Host",
                                                            "If-Modified-Since",
                                                            "Range",
                                                            "Referer",
                                                            "Transfer-Encoding",
                                                            "User-Agent",
                                                            "Proxy-Connection"
                                                        };

        #region singleton

        private class RESTesterLogicInstance
        {
            internal static readonly RESTesterLogic Instance = new RESTesterLogic();

            static RESTesterLogicInstance() { }
        }

        public static RESTesterLogic Instance
        {
            get
            {
                return RESTesterLogicInstance.Instance;
            }
        }

        private RESTesterLogic() { }

        #endregion

        #region RequestStart event

        public delegate void RequestStartHandler(List<HTTPHeader> headers);

        public event RequestStartHandler RequestStart;

        private void OnRequestStart(List<HTTPHeader> headers)
        {
            if (RequestStart != null)
            {
                RequestStart(headers);
            }
        }

        #endregion

        #region RequestEnd event

        public delegate void RequestEndHandler(string plainText, string xml, string json, List<HTTPHeader> headers);

        public event RequestEndHandler RequestEnd;

        private void OnRequestEnd(string plainText, string xml, string json, List<HTTPHeader> headers)
        {
            if (RequestEnd != null)
            {
                RequestEnd(plainText, xml, json, headers);
            }
        }

        #endregion

        #region Log event

        public delegate void LogHandler(string message);

        public event LogHandler Log;

        private void OnLog(string message)
        {
            if (Log != null)
            {
                Log(message);
            }
        }

        #endregion

        public void DoRequest(string url, HTTPMethod method, List<HTTPHeader> headers = null, string contentType = null, string requestBody = null)
        {
            new Thread(() => DoRequestInThread(url, method, headers, contentType, requestBody)).Start();               
        }

        public bool SaveToDb(Request obj)
        {
            bool result = false;

            if (!RESTesterLogic.Instance.DbExists())
            {
                RESTesterLogic.Instance.CreateDb();
            }
            try
            {
                using (SessionNoServer session = new SessionNoServer(Environment.CurrentDirectory, enableCache: false))
                {
                    var needToIncrementId = false;
                    session.BeginUpdate();

                    Database db = session.OpenDatabase(Request.PlaceInDatabase);

                    Request request = db.AllObjects<Request>().Where(item => item.Id == obj.Id).SingleOrDefault();
                    if (request != null)
                    {
                        obj.Id = request.Id;
                        request.Unpersist(session);
                        needToIncrementId = false;
                    }
                    else
                    {
                        obj.Id = _lastId + 1;
                        needToIncrementId = true;
                    }

                    obj.Persist(session, obj);
                    session.Commit();
                    
                    if (needToIncrementId)
                    {
                        _lastId++;
                    }

                    result = true;

                    OnLog(needToIncrementId ? "Request saved." : "Request updated.");
                }
            }
            
            catch { }

            return result;
        }

        public bool DbExists()
        {
            bool result = false;
            using (SessionNoServer session = new SessionNoServer(Environment.CurrentDirectory, enableCache: false))
            {
                try
                {
                    session.BeginRead();
                    result = true;
                }
                catch { }
            }

            return result;
        }

        public bool CreateDb()
        {
            bool result = false;
            try
            {
                using (SessionNoServer session = new SessionNoServer(Environment.CurrentDirectory, enableCache: false))
                {
                    session.BeginUpdate();
                    Database db = session.NewDatabase(Request.PlaceInDatabase, name: "Requests");
                    session.Commit();

                    result = true;

                    OnLog("DB created.");
                }
            }
            catch { }

            return result;
        }

        public bool LoadFromDb(out List<Request> requests)
        {
            bool result = false;
            requests = new List<Request>();
            try
            {
                using (SessionNoServer session = new SessionNoServer(Environment.CurrentDirectory, enableCache: false))
                {
                    session.BeginRead();
                    Database db = session.OpenDatabase(Request.PlaceInDatabase);
                    requests = (from Request request in db.AllObjects<Request>() select request).ToList<Request>();
                    _lastId = requests.Last().Id;
                    result = true;

                    OnLog("Requests list are loaded.");
                }
            }
            catch { }

            return result;
        }

        public bool RemoveFromDB(Request obj)
        {
            bool result = false;
            try
            {
                using (SessionNoServer session = new SessionNoServer(Environment.CurrentDirectory, enableCache: false))
                {
                    session.BeginUpdate();
                    if (session.Open(Request.PlaceInDatabase, 1, 1, true) != null)
                    {
                        Database db = session.OpenDatabase(Request.PlaceInDatabase);
                        Request request = db.AllObjects<Request>().Where(item => item.Id == obj.Id).Single();
                        request.Unpersist(session);
                        session.Commit();
                        result = true;

                        OnLog("Request deleted.");
                    }
                }
            }

            catch { }

            return result;
        }

        private void DoRequestInThread(string url, HTTPMethod method, List<HTTPHeader> headers = null, string contentType = null, string requestBody = null)
        {
            RequestState requestState = new RequestState();
            requestState.Request = HttpWebRequest.Create(url);
            requestState.RequestData = requestBody;

            if (headers != null)
            {
                foreach (var item in headers)
                {
                    if (!_restrictedHeaders.Contains(item.Name))
                    {
                        requestState.Request.Headers.Add(item.Name, item.Value);
                    }
                    else
                    {
                        OnLog(string.Format("Header '{0}' are considered restricted and is either exposed directly by the API or protected by the system and cannot be changed.", item.Name));
                    }
                }
            }

            OnRequestStart(requestState.Request.Headers.GetParsedHeaders());

            requestState.Request.ContentType = contentType ?? "application/x-www-form-urlencoded";
            requestState.Request.Method = method.ToString();

            if (method == HTTPMethod.POST || method == HTTPMethod.PUT)
            {
                if (string.IsNullOrEmpty(requestState.RequestData))
                {
                    OnLog("Request body are empty!.");
                }
                else
                {
                    byte[] byteArray = Encoding.UTF8.GetBytes(requestState.RequestData);

                    requestState.Request.ContentLength = byteArray.Length;
                    Stream dataStream = requestState.Request.GetRequestStream();
                    dataStream.Write(byteArray, 0, byteArray.Length);
                    dataStream.Close();
                }
            }

            WebResponse myWebResponse = requestState.Request.GetResponse();
            Stream streamResponse = myWebResponse.GetResponseStream();
            StreamReader streamRead = new StreamReader(streamResponse);
            Char[] readBuff = new Char[256];
            int count = streamRead.Read(readBuff, 0, 256);

            StringBuilder responseContent = new StringBuilder();
            while (count > 0)
            {
                responseContent.Append(new String(readBuff, 0, count));
                count = streamRead.Read(readBuff, 0, 256);
            }

            OnRequestEnd(responseContent.ToString(),
                        DataPresentationFormatter.Format(responseContent.ToString(), DataPresentationFormat.XML),
                        DataPresentationFormatter.Format(responseContent.ToString(), DataPresentationFormat.JSON),
                        myWebResponse.Headers.GetParsedHeaders());

            streamResponse.Close();
            streamRead.Close();
            myWebResponse.Close();
        }
    }
}
